近日写一软件,遇到了高DPI下界面错乱的问题,在网上搜索了好几天,都没有满意的解决方法。也下载了一些坛友的解决方案示例,其基本思路是按比例将高DPI下控件的位置及大小恢复为默认DPI下的位置及大小,经实验,这种方法对简单界面是有效的,当界面比较复杂,控件比较多时,仍会错乱。
反复对比计算不同DPI下的控件大小及位置,发现实在是摸不透WINDOWS对高DPI下的控件是如何调整其位置及大小的,完全没有固定的比例,所以坛友的解决方案只能将部件控件的位置予以恢复,大小比原来小了许多,另一些则位置也不正确。
在其它论坛及博客等也查看了些类似的文章,始终是无法解决。感觉有个网友说的很正确:说能完全解决高DPI界面错乱问题的都是牛鬼蛇神!
问题总得解决,思路还是让高DPI下控件恢复到默认DPI时位置及大小,即然无法按比例调整,何不我就记录下控件原有位置!
说干就干。第一次,在窗口初始化完成后,我调用一个函数,枚举所有子窗口,记录下其位置,生成一个表格,然后保存到文件中,之后再把这个文件加入软件。以后每次启动软件,就按这个表格调整窗口所有控件的大小及位置。界面终于不再错乱了。
至于字体,按比例调整是没有问题的,所以字体信息无需记录。
当然这个解决方案不是完美的,因为控件大小及字体一直是默认DPI下的大小,所以在高DPI下显得与整个桌面不协调,就是比桌面上其它软件的字体要小。这个要解决也行,就是把所有控件按自定的比例缩放。不过我嫌麻烦,没去做。
其次就是,由系统设置的一些窗口没有调整,比如标题框、弹出的对话框、右键菜单等,所以在你的软件上会有些字大,有些字小。
但至少不错乱了!
附枚举窗口及调整字体的代码:
#ifdef DEBUG_WINDOW_LIST //控件列表 typedef struct structWindowItem { UINT CtrlId; CHAR szClassName[STRING_SPACE]; RECT Location; }WNDITEM; typedef struct structWindowList { HWND ParentHwnd; UINT Count; WNDITEM Item[WINDOWS_MAX]; }WNDLIST; WNDLIST strWindowList; #define STRING_SPACE 256 // 枚举子窗口 int ChildWindowList(HWND hwnd) { strWindowList.ParentHwnd = hwnd; strWindowList.Count = 0; memset(strWindowList.Item, 0, sizeof(strWindowList.Item)); ::EnumChildWindows(hwnd, ChildWindowProcess, NULL); //将子窗口参数保存到文件 #if 0 CStdioFile cFileList; if(cFileList.Open(_T("window.list"), CFile::modeCreate|CFile::modeWrite|CFile::shareDenyNone|CFile::typeText)) { int nLength; char szBuffer[STRING_SPACE]; char szClassName[STRING_SPACE]; cFileList.WriteString(_T("const WNDITEM WindowsList[] = \n{\n")); for(UINT nIndex = 0; nIndex < strWindowList.Count; nIndex++) { memset(szBuffer, 0, sizeof(szBuffer)); memset(szClassName, 0x20, sizeof(szBuffer)); nLength = strlen(strWindowList.Item[nIndex].szClassName); szClassName[0] = _T('\"'); memcpy(szClassName + 1, strWindowList.Item[nIndex].szClassName, nLength); szClassName[nLength + 1] = _T('\"'); szClassName[nLength + 2] = _T(','); szClassName[15] = 0; sprintf_s(szBuffer, _T(" {%4d, %s {%4d, %4d, %4d, %4d}},\n"), strWindowList.Item[nIndex].CtrlId, szClassName, strWindowList.Item[nIndex].Location.left, strWindowList.Item[nIndex].Location.top, strWindowList.Item[nIndex].Location.right, strWindowList.Item[nIndex].Location.bottom); cFileList.WriteString(szBuffer); } cFileList.WriteString(_T("};\n")); cFileList.Close(); } #endif return strWindowList.Count; } // 枚举子窗口调用 BOOL CALLBACK ChildWindowProcess(HWND hwnd, LPARAM lParam) { int nIndex; if((hwnd != NULL) && (strWindowList.Count < WINDOWS_MAX)) { nIndex = strWindowList.Count; //ID strWindowList.Item[nIndex].CtrlId = ::GetDlgCtrlID(hwnd); //类名 ::GetClassName(hwnd, strWindowList.Item[nIndex].szClassName, STRING_SPACE); //位置 ::GetWindowRect(hwnd, &strWindowList.Item[nIndex].Location); //转换为窗口内坐标 CWnd *pWnd = CWnd::FromHandle(strWindowList.ParentHwnd); pWnd->ScreenToClient(&strWindowList.Item[nIndex].Location); strWindowList.Count++; } return TRUE; } #endif //调整字体 #define DEFAULT_DPI 96.0 BOOL ChildWindowFontRestore(HWND hChildWnd ) { LOGFONT LgFont; int DpiY; HDC hDC = ::GetDC(NULL); if(hDC != NULL) { DpiY= GetDeviceCaps(hDC, LOGPIXELSY); ::ReleaseDC(NULL, hDC); } double dbScale = (double)DEFAULT_DPI / DpiY; //获取当前字体 HFONT hFont = (HFONT)::SendMessage(hChildWnd, WM_GETFONT, NULL, NULL); if(hFont != NULL) { //获取字体信息 ::GetObject(hFont, sizeof(LOGFONT), &LgFont); //按比例修改大小 LgFont.lfHeight = LONG( (double)LgFont.lfHeight * dbScale + 0.5f); //生成物新的字体 hFont = ::CreateFontIndirect(&LgFont); if(hFont != NULL) { //重设字体 SendMessage(hChildWnd, WM_SETFONT, (LPARAM)hFont, TRUE); } } return TRUE; }
HFONT的保存及释放过程我没有写出来,实际应用中建立的字体句柄要保存,在程序结束时释放。
还有就是,若程序是在WIN8以上系统下运行,这样调的结果可能反倒导致界面变乱,貌似WIN8以上系统已解决了这个问题,所以在软件里应加上系统检测,在WIN8以上就交由系统去处理。
本页共127段,4448个字符,6309 Byte(字节)